home *** CD-ROM | disk | FTP | other *** search
/ Personal Computer World 2009 February / PCWFEB09.iso / Software / Linux / Kubuntu 8.10 / kubuntu-8.10-desktop-i386.iso / casper / filesystem.squashfs / usr / lib / ruby / 1.8 / fileutils.rb < prev    next >
Text File  |  2007-03-04  |  43KB  |  1,592 lines

  1. # = fileutils.rb
  2. # Copyright (c) 2000-2006 Minero Aoki
  3. # This program is free software.
  4. # You can distribute/modify this program under the same terms of ruby.
  5. # == module FileUtils
  6. # Namespace for several file utility methods for copying, moving, removing, etc.
  7. # === Module Functions
  8. #   cd(dir, options)
  9. #   cd(dir, options) {|dir| .... }
  10. #   pwd()
  11. #   mkdir(dir, options)
  12. #   mkdir(list, options)
  13. #   mkdir_p(dir, options)
  14. #   mkdir_p(list, options)
  15. #   rmdir(dir, options)
  16. #   rmdir(list, options)
  17. #   ln(old, new, options)
  18. #   ln(list, destdir, options)
  19. #   ln_s(old, new, options)
  20. #   ln_s(list, destdir, options)
  21. #   ln_sf(src, dest, options)
  22. #   cp(src, dest, options)
  23. #   cp(list, dir, options)
  24. #   cp_r(src, dest, options)
  25. #   cp_r(list, dir, options)
  26. #   mv(src, dest, options)
  27. #   mv(list, dir, options)
  28. #   rm(list, options)
  29. #   rm_r(list, options)
  30. #   rm_rf(list, options)
  31. #   install(src, dest, mode = <src's>, options)
  32. #   chmod(mode, list, options)
  33. #   chmod_R(mode, list, options)
  34. #   chown(user, group, list, options)
  35. #   chown_R(user, group, list, options)
  36. #   touch(list, options)
  37. #
  38. # The <tt>options</tt> parameter is a hash of options, taken from the list
  39. # <tt>:force</tt>, <tt>:noop</tt>, <tt>:preserve</tt>, and <tt>:verbose</tt>.
  40. # <tt>:noop</tt> means that no changes are made.  The other two are obvious.
  41. # Each method documents the options that it honours.
  42. #
  43. # All methods that have the concept of a "source" file or directory can take
  44. # either one file or a list of files in that argument.  See the method
  45. # documentation for examples.
  46. #
  47. # There are some `low level' methods, which do not accept any option:
  48. #
  49. #   copy_entry(src, dest, preserve = false, dereference = false)
  50. #   copy_file(src, dest, preserve = false, dereference = true)
  51. #   copy_stream(srcstream, deststream)
  52. #   remove_entry(path, force = false)
  53. #   remove_entry_secure(path, force = false)
  54. #   remove_file(path, force = false)
  55. #   compare_file(path_a, path_b)
  56. #   compare_stream(stream_a, stream_b)
  57. #   uptodate?(file, cmp_list)
  58. #
  59. # == module FileUtils::Verbose
  60. # This module has all methods of FileUtils module, but it outputs messages
  61. # before acting.  This equates to passing the <tt>:verbose</tt> flag to methods
  62. # in FileUtils.
  63. # == module FileUtils::NoWrite
  64. # This module has all methods of FileUtils module, but never changes
  65. # files/directories.  This equates to passing the <tt>:noop</tt> flag to methods
  66. # in FileUtils.
  67. # == module FileUtils::DryRun
  68. # This module has all methods of FileUtils module, but never changes
  69. # files/directories.  This equates to passing the <tt>:noop</tt> and
  70. # <tt>:verbose</tt> flags to methods in FileUtils.
  71.  
  72. module FileUtils
  73.  
  74.   def self.private_module_function(name)   #:nodoc:
  75.     module_function name
  76.     private_class_method name
  77.   end
  78.  
  79.   # This hash table holds command options.
  80.   OPT_TABLE = {}   #:nodoc: internal use only
  81.  
  82.   #
  83.   # Options: (none)
  84.   #
  85.   # Returns the name of the current directory.
  86.   #
  87.   def pwd
  88.     Dir.pwd
  89.   end
  90.   module_function :pwd
  91.  
  92.   alias getwd pwd
  93.   module_function :getwd
  94.  
  95.   #
  96.   # Options: verbose
  97.   # 
  98.   # Changes the current directory to the directory +dir+.
  99.   # 
  100.   # If this method is called with block, resumes to the old
  101.   # working directory after the block execution finished.
  102.   # 
  103.   #   FileUtils.cd('/', :verbose => true)   # chdir and report it
  104.   # 
  105.   def cd(dir, options = {}, &block) # :yield: dir
  106.     fu_check_options options, OPT_TABLE['cd']
  107.     fu_output_message "cd #{dir}" if options[:verbose]
  108.     Dir.chdir(dir, &block)
  109.     fu_output_message 'cd -' if options[:verbose] and block
  110.   end
  111.   module_function :cd
  112.  
  113.   alias chdir cd
  114.   module_function :chdir
  115.  
  116.   OPT_TABLE['cd']    =
  117.   OPT_TABLE['chdir'] = [:verbose]
  118.  
  119.   #
  120.   # Options: (none)
  121.   # 
  122.   # Returns true if +newer+ is newer than all +old_list+.
  123.   # Non-existent files are older than any file.
  124.   # 
  125.   #   FileUtils.uptodate?('hello.o', %w(hello.c hello.h)) or \
  126.   #       system 'make hello.o'
  127.   # 
  128.   def uptodate?(new, old_list, options = nil)
  129.     raise ArgumentError, 'uptodate? does not accept any option' if options
  130.  
  131.     return false unless File.exist?(new)
  132.     new_time = File.mtime(new)
  133.     old_list.each do |old|
  134.       if File.exist?(old)
  135.         return false unless new_time > File.mtime(old)
  136.       end
  137.     end
  138.     true
  139.   end
  140.   module_function :uptodate?
  141.  
  142.   #
  143.   # Options: mode noop verbose
  144.   # 
  145.   # Creates one or more directories.
  146.   # 
  147.   #   FileUtils.mkdir 'test'
  148.   #   FileUtils.mkdir %w( tmp data )
  149.   #   FileUtils.mkdir 'notexist', :noop => true  # Does not really create.
  150.   #   FileUtils.mkdir 'tmp', :mode => 0700
  151.   # 
  152.   def mkdir(list, options = {})
  153.     fu_check_options options, OPT_TABLE['mkdir']
  154.     list = fu_list(list)
  155.     fu_output_message "mkdir #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  156.     return if options[:noop]
  157.  
  158.     list.each do |dir|
  159.       fu_mkdir dir, options[:mode]
  160.     end
  161.   end
  162.   module_function :mkdir
  163.  
  164.   OPT_TABLE['mkdir'] = [:mode, :noop, :verbose]
  165.  
  166.   #
  167.   # Options: mode noop verbose
  168.   # 
  169.   # Creates a directory and all its parent directories.
  170.   # For example,
  171.   # 
  172.   #   FileUtils.mkdir_p '/usr/local/lib/ruby'
  173.   # 
  174.   # causes to make following directories, if it does not exist.
  175.   #     * /usr
  176.   #     * /usr/local
  177.   #     * /usr/local/lib
  178.   #     * /usr/local/lib/ruby
  179.   #
  180.   # You can pass several directories at a time in a list.
  181.   # 
  182.   def mkdir_p(list, options = {})
  183.     fu_check_options options, OPT_TABLE['mkdir_p']
  184.     list = fu_list(list)
  185.     fu_output_message "mkdir -p #{options[:mode] ? ('-m %03o ' % options[:mode]) : ''}#{list.join ' '}" if options[:verbose]
  186.     return *list if options[:noop]
  187.  
  188.     list.map {|path| path.sub(%r</\z>, '') }.each do |path|
  189.       # optimize for the most common case
  190.       begin
  191.         fu_mkdir path, options[:mode]
  192.         next
  193.       rescue SystemCallError
  194.         next if File.directory?(path)
  195.       end
  196.  
  197.       stack = []
  198.       until path == stack.last   # dirname("/")=="/", dirname("C:/")=="C:/"
  199.         stack.push path
  200.         path = File.dirname(path)
  201.       end
  202.       stack.reverse_each do |path|
  203.         begin
  204.           fu_mkdir path, options[:mode]
  205.         rescue SystemCallError => err
  206.           raise unless File.directory?(path)
  207.         end
  208.       end
  209.     end
  210.  
  211.     return *list
  212.   end
  213.   module_function :mkdir_p
  214.  
  215.   alias mkpath    mkdir_p
  216.   alias makedirs  mkdir_p
  217.   module_function :mkpath
  218.   module_function :makedirs
  219.  
  220.   OPT_TABLE['mkdir_p']  =
  221.   OPT_TABLE['mkpath']   =
  222.   OPT_TABLE['makedirs'] = [:mode, :noop, :verbose]
  223.  
  224.   def fu_mkdir(path, mode)   #:nodoc:
  225.     path = path.sub(%r</\z>, '')
  226.     if mode
  227.       Dir.mkdir path, mode
  228.       File.chmod mode, path
  229.     else
  230.       Dir.mkdir path
  231.     end
  232.   end
  233.   private_module_function :fu_mkdir
  234.  
  235.   #
  236.   # Options: noop, verbose
  237.   # 
  238.   # Removes one or more directories.
  239.   # 
  240.   #   FileUtils.rmdir 'somedir'
  241.   #   FileUtils.rmdir %w(somedir anydir otherdir)
  242.   #   # Does not really remove directory; outputs message.
  243.   #   FileUtils.rmdir 'somedir', :verbose => true, :noop => true
  244.   # 
  245.   def rmdir(list, options = {})
  246.     fu_check_options options, OPT_TABLE['rmdir']
  247.     list = fu_list(list)
  248.     fu_output_message "rmdir #{list.join ' '}" if options[:verbose]
  249.     return if options[:noop]
  250.     list.each do |dir|
  251.       Dir.rmdir dir.sub(%r</\z>, '')
  252.     end
  253.   end
  254.   module_function :rmdir
  255.  
  256.   OPT_TABLE['rmdir'] = [:noop, :verbose]
  257.  
  258.   #
  259.   # Options: force noop verbose
  260.   #
  261.   # <b><tt>ln(old, new, options = {})</tt></b>
  262.   #
  263.   # Creates a hard link +new+ which points to +old+.
  264.   # If +new+ already exists and it is a directory, creates a link +new/old+.
  265.   # If +new+ already exists and it is not a directory, raises Errno::EEXIST.
  266.   # But if :force option is set, overwrite +new+.
  267.   # 
  268.   #   FileUtils.ln 'gcc', 'cc', :verbose => true
  269.   #   FileUtils.ln '/usr/bin/emacs21', '/usr/bin/emacs'
  270.   # 
  271.   # <b><tt>ln(list, destdir, options = {})</tt></b>
  272.   # 
  273.   # Creates several hard links in a directory, with each one pointing to the
  274.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  275.   # 
  276.   #   include FileUtils
  277.   #   cd '/sbin'
  278.   #   FileUtils.ln %w(cp mv mkdir), '/bin'   # Now /sbin/cp and /bin/cp are linked.
  279.   # 
  280.   def ln(src, dest, options = {})
  281.     fu_check_options options, OPT_TABLE['ln']
  282.     fu_output_message "ln#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  283.     return if options[:noop]
  284.     fu_each_src_dest0(src, dest) do |s,d|
  285.       remove_file d, true if options[:force]
  286.       File.link s, d
  287.     end
  288.   end
  289.   module_function :ln
  290.  
  291.   alias link ln
  292.   module_function :link
  293.  
  294.   OPT_TABLE['ln']   =
  295.   OPT_TABLE['link'] = [:force, :noop, :verbose]
  296.  
  297.   #
  298.   # Options: force noop verbose
  299.   #
  300.   # <b><tt>ln_s(old, new, options = {})</tt></b>
  301.   # 
  302.   # Creates a symbolic link +new+ which points to +old+.  If +new+ already
  303.   # exists and it is a directory, creates a symbolic link +new/old+.  If +new+
  304.   # already exists and it is not a directory, raises Errno::EEXIST.  But if
  305.   # :force option is set, overwrite +new+.
  306.   # 
  307.   #   FileUtils.ln_s '/usr/bin/ruby', '/usr/local/bin/ruby'
  308.   #   FileUtils.ln_s 'verylongsourcefilename.c', 'c', :force => true
  309.   # 
  310.   # <b><tt>ln_s(list, destdir, options = {})</tt></b>
  311.   # 
  312.   # Creates several symbolic links in a directory, with each one pointing to the
  313.   # item in +list+.  If +destdir+ is not a directory, raises Errno::ENOTDIR.
  314.   #
  315.   # If +destdir+ is not a directory, raises Errno::ENOTDIR.
  316.   # 
  317.   #   FileUtils.ln_s Dir.glob('bin/*.rb'), '/home/aamine/bin'
  318.   # 
  319.   def ln_s(src, dest, options = {})
  320.     fu_check_options options, OPT_TABLE['ln_s']
  321.     fu_output_message "ln -s#{options[:force] ? 'f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  322.     return if options[:noop]
  323.     fu_each_src_dest0(src, dest) do |s,d|
  324.       remove_file d, true if options[:force]
  325.       File.symlink s, d
  326.     end
  327.   end
  328.   module_function :ln_s
  329.  
  330.   alias symlink ln_s
  331.   module_function :symlink
  332.  
  333.   OPT_TABLE['ln_s']    =
  334.   OPT_TABLE['symlink'] = [:force, :noop, :verbose]
  335.  
  336.   #
  337.   # Options: noop verbose
  338.   # 
  339.   # Same as
  340.   #   #ln_s(src, dest, :force)
  341.   # 
  342.   def ln_sf(src, dest, options = {})
  343.     fu_check_options options, OPT_TABLE['ln_sf']
  344.     options = options.dup
  345.     options[:force] = true
  346.     ln_s src, dest, options
  347.   end
  348.   module_function :ln_sf
  349.  
  350.   OPT_TABLE['ln_sf'] = [:noop, :verbose]
  351.  
  352.   #
  353.   # Options: preserve noop verbose
  354.   #
  355.   # Copies a file content +src+ to +dest+.  If +dest+ is a directory,
  356.   # copies +src+ to +dest/src+.
  357.   #
  358.   # If +src+ is a list of files, then +dest+ must be a directory.
  359.   #
  360.   #   FileUtils.cp 'eval.c', 'eval.c.org'
  361.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6'
  362.   #   FileUtils.cp %w(cgi.rb complex.rb date.rb), '/usr/lib/ruby/1.6', :verbose => true
  363.   #   FileUtils.cp 'symlink', 'dest'   # copy content, "dest" is not a symlink
  364.   # 
  365.   def cp(src, dest, options = {})
  366.     fu_check_options options, OPT_TABLE['cp']
  367.     fu_output_message "cp#{options[:preserve] ? ' -p' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  368.     return if options[:noop]
  369.     fu_each_src_dest(src, dest) do |s, d|
  370.       copy_file s, d, options[:preserve]
  371.     end
  372.   end
  373.   module_function :cp
  374.  
  375.   alias copy cp
  376.   module_function :copy
  377.  
  378.   OPT_TABLE['cp']   =
  379.   OPT_TABLE['copy'] = [:preserve, :noop, :verbose]
  380.  
  381.   #
  382.   # Options: preserve noop verbose dereference_root remove_destination
  383.   # 
  384.   # Copies +src+ to +dest+. If +src+ is a directory, this method copies
  385.   # all its contents recursively. If +dest+ is a directory, copies
  386.   # +src+ to +dest/src+.
  387.   #
  388.   # +src+ can be a list of files.
  389.   # 
  390.   #   # Installing ruby library "mylib" under the site_ruby
  391.   #   FileUtils.rm_r site_ruby + '/mylib', :force
  392.   #   FileUtils.cp_r 'lib/', site_ruby + '/mylib'
  393.   # 
  394.   #   # Examples of copying several files to target directory.
  395.   #   FileUtils.cp_r %w(mail.rb field.rb debug/), site_ruby + '/tmail'
  396.   #   FileUtils.cp_r Dir.glob('*.rb'), '/home/aamine/lib/ruby', :noop => true, :verbose => true
  397.   #
  398.   #   # If you want to copy all contents of a directory instead of the
  399.   #   # directory itself, c.f. src/x -> dest/x, src/y -> dest/y,
  400.   #   # use following code.
  401.   #   FileUtils.cp_r 'src/.', 'dest'     # cp_r('src', 'dest') makes src/dest,
  402.   #                                      # but this doesn't.
  403.   # 
  404.   def cp_r(src, dest, options = {})
  405.     fu_check_options options, OPT_TABLE['cp_r']
  406.     fu_output_message "cp -r#{options[:preserve] ? 'p' : ''}#{options[:remove_destination] ? ' --remove-destination' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  407.     return if options[:noop]
  408.     options[:dereference_root] = true unless options.key?(:dereference_root)
  409.     fu_each_src_dest(src, dest) do |s, d|
  410.       copy_entry s, d, options[:preserve], options[:dereference_root], options[:remove_destination]
  411.     end
  412.   end
  413.   module_function :cp_r
  414.  
  415.   OPT_TABLE['cp_r'] = [:preserve, :noop, :verbose,
  416.                        :dereference_root, :remove_destination]
  417.  
  418.   #
  419.   # Copies a file system entry +src+ to +dest+.
  420.   # If +src+ is a directory, this method copies its contents recursively.
  421.   # This method preserves file types, c.f. symlink, directory...
  422.   # (FIFO, device files and etc. are not supported yet)
  423.   #
  424.   # Both of +src+ and +dest+ must be a path name.
  425.   # +src+ must exist, +dest+ must not exist.
  426.   #
  427.   # If +preserve+ is true, this method preserves owner, group, permissions
  428.   # and modified time.
  429.   #
  430.   # If +dereference_root+ is true, this method dereference tree root.
  431.   #
  432.   # If +remove_destination+ is true, this method removes each destination file before copy.
  433.   #
  434.   def copy_entry(src, dest, preserve = false, dereference_root = false, remove_destination = false)
  435.     Entry_.new(src, nil, dereference_root).traverse do |ent|
  436.       destent = Entry_.new(dest, ent.rel, false)
  437.       File.unlink destent.path if remove_destination && File.file?(destent.path)
  438.       ent.copy destent.path
  439.       ent.copy_metadata destent.path if preserve
  440.     end
  441.   end
  442.   module_function :copy_entry
  443.  
  444.   #
  445.   # Copies file contents of +src+ to +dest+.
  446.   # Both of +src+ and +dest+ must be a path name.
  447.   #
  448.   def copy_file(src, dest, preserve = false, dereference = true)
  449.     ent = Entry_.new(src, nil, dereference)
  450.     ent.copy_file dest
  451.     ent.copy_metadata dest if preserve
  452.   end
  453.   module_function :copy_file
  454.  
  455.   #
  456.   # Copies stream +src+ to +dest+.
  457.   # +src+ must respond to #read(n) and
  458.   # +dest+ must respond to #write(str).
  459.   #
  460.   def copy_stream(src, dest)
  461.     fu_copy_stream0 src, dest, fu_stream_blksize(src, dest)
  462.   end
  463.   module_function :copy_stream
  464.  
  465.   #
  466.   # Options: force noop verbose
  467.   # 
  468.   # Moves file(s) +src+ to +dest+.  If +file+ and +dest+ exist on the different
  469.   # disk partition, the file is copied instead.
  470.   # 
  471.   #   FileUtils.mv 'badname.rb', 'goodname.rb'
  472.   #   FileUtils.mv 'stuff.rb', '/notexist/lib/ruby', :force => true  # no error
  473.   # 
  474.   #   FileUtils.mv %w(junk.txt dust.txt), '/home/aamine/.trash/'
  475.   #   FileUtils.mv Dir.glob('test*.rb'), 'test', :noop => true, :verbose => true
  476.   # 
  477.   def mv(src, dest, options = {})
  478.     fu_check_options options, OPT_TABLE['mv']
  479.     fu_output_message "mv#{options[:force] ? ' -f' : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  480.     return if options[:noop]
  481.     fu_each_src_dest(src, dest) do |s, d|
  482.       destent = Entry_.new(d, nil, true)
  483.       begin
  484.         if destent.exist?
  485.           if destent.directory?
  486.             raise Errno::EEXIST, dest
  487.           else
  488.             destent.remove_file if rename_cannot_overwrite_file?
  489.           end
  490.         end
  491.         begin
  492.           File.rename s, d
  493.         rescue Errno::EXDEV
  494.           copy_entry s, d, true
  495.           if options[:secure]
  496.             remove_entry_secure s, options[:force]
  497.           else
  498.             remove_entry s, options[:force]
  499.           end
  500.         end
  501.       rescue SystemCallError
  502.         raise unless options[:force]
  503.       end
  504.     end
  505.   end
  506.   module_function :mv
  507.  
  508.   alias move mv
  509.   module_function :move
  510.  
  511.   OPT_TABLE['mv']   =
  512.   OPT_TABLE['move'] = [:force, :noop, :verbose, :secure]
  513.  
  514.   def rename_cannot_overwrite_file?   #:nodoc:
  515.     /djgpp|cygwin|mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  516.   end
  517.   private_module_function :rename_cannot_overwrite_file?
  518.  
  519.   #
  520.   # Options: force noop verbose
  521.   # 
  522.   # Remove file(s) specified in +list+.  This method cannot remove directories.
  523.   # All StandardErrors are ignored when the :force option is set.
  524.   # 
  525.   #   FileUtils.rm %w( junk.txt dust.txt )
  526.   #   FileUtils.rm Dir.glob('*.so')
  527.   #   FileUtils.rm 'NotExistFile', :force => true   # never raises exception
  528.   # 
  529.   def rm(list, options = {})
  530.     fu_check_options options, OPT_TABLE['rm']
  531.     list = fu_list(list)
  532.     fu_output_message "rm#{options[:force] ? ' -f' : ''} #{list.join ' '}" if options[:verbose]
  533.     return if options[:noop]
  534.  
  535.     list.each do |path|
  536.       remove_file path, options[:force]
  537.     end
  538.   end
  539.   module_function :rm
  540.  
  541.   alias remove rm
  542.   module_function :remove
  543.  
  544.   OPT_TABLE['rm']     =
  545.   OPT_TABLE['remove'] = [:force, :noop, :verbose]
  546.  
  547.   #
  548.   # Options: noop verbose
  549.   # 
  550.   # Equivalent to
  551.   #
  552.   #   #rm(list, :force => true)
  553.   #
  554.   def rm_f(list, options = {})
  555.     fu_check_options options, OPT_TABLE['rm_f']
  556.     options = options.dup
  557.     options[:force] = true
  558.     rm list, options
  559.   end
  560.   module_function :rm_f
  561.  
  562.   alias safe_unlink rm_f
  563.   module_function :safe_unlink
  564.  
  565.   OPT_TABLE['rm_f']        =
  566.   OPT_TABLE['safe_unlink'] = [:noop, :verbose]
  567.  
  568.   #
  569.   # Options: force noop verbose secure
  570.   # 
  571.   # remove files +list+[0] +list+[1]... If +list+[n] is a directory,
  572.   # removes its all contents recursively. This method ignores
  573.   # StandardError when :force option is set.
  574.   # 
  575.   #   FileUtils.rm_r Dir.glob('/tmp/*')
  576.   #   FileUtils.rm_r '/', :force => true          #  :-)
  577.   #
  578.   # WARNING: This method causes local vulnerability
  579.   # if one of parent directories or removing directory tree are world
  580.   # writable (including /tmp, whose permission is 1777), and the current
  581.   # process has strong privilege such as Unix super user (root), and the
  582.   # system has symbolic link.  For secure removing, read the documentation
  583.   # of #remove_entry_secure carefully, and set :secure option to true.
  584.   # Default is :secure=>false.
  585.   #
  586.   # NOTE: This method calls #remove_entry_secure if :secure option is set.
  587.   # See also #remove_entry_secure.
  588.   # 
  589.   def rm_r(list, options = {})
  590.     fu_check_options options, OPT_TABLE['rm_r']
  591.     # options[:secure] = true unless options.key?(:secure)
  592.     list = fu_list(list)
  593.     fu_output_message "rm -r#{options[:force] ? 'f' : ''} #{list.join ' '}" if options[:verbose]
  594.     return if options[:noop]
  595.     list.each do |path|
  596.       if options[:secure]
  597.         remove_entry_secure path, options[:force]
  598.       else
  599.         remove_entry path, options[:force]
  600.       end
  601.     end
  602.   end
  603.   module_function :rm_r
  604.  
  605.   OPT_TABLE['rm_r'] = [:force, :noop, :verbose, :secure]
  606.  
  607.   #
  608.   # Options: noop verbose secure
  609.   # 
  610.   # Equivalent to
  611.   #
  612.   #   #rm_r(list, :force => true)
  613.   #
  614.   # WARNING: This method causes local vulnerability.
  615.   # Read the documentation of #rm_r first.
  616.   # 
  617.   def rm_rf(list, options = {})
  618.     fu_check_options options, OPT_TABLE['rm_rf']
  619.     options = options.dup
  620.     options[:force] = true
  621.     rm_r list, options
  622.   end
  623.   module_function :rm_rf
  624.  
  625.   alias rmtree rm_rf
  626.   module_function :rmtree
  627.  
  628.   OPT_TABLE['rm_rf']  =
  629.   OPT_TABLE['rmtree'] = [:noop, :verbose, :secure]
  630.  
  631.   #
  632.   # This method removes a file system entry +path+.  +path+ shall be a
  633.   # regular file, a directory, or something.  If +path+ is a directory,
  634.   # remove it recursively.  This method is required to avoid TOCTTOU
  635.   # (time-of-check-to-time-of-use) local security vulnerability of #rm_r.
  636.   # #rm_r causes security hole when:
  637.   #
  638.   #   * Parent directory is world writable (including /tmp).
  639.   #   * Removing directory tree includes world writable directory.
  640.   #   * The system has symbolic link.
  641.   #
  642.   # To avoid this security hole, this method applies special preprocess.
  643.   # If +path+ is a directory, this method chown(2) and chmod(2) all
  644.   # removing directories.  This requires the current process is the
  645.   # owner of the removing whole directory tree, or is the super user (root).
  646.   #
  647.   # WARNING: You must ensure that *ALL* parent directories are not
  648.   # world writable.  Otherwise this method does not work.
  649.   # Only exception is temporary directory like /tmp and /var/tmp,
  650.   # whose permission is 1777.
  651.   #
  652.   # WARNING: Only the owner of the removing directory tree, or Unix super
  653.   # user (root) should invoke this method.  Otherwise this method does not
  654.   # work.
  655.   #
  656.   # For details of this security vulnerability, see Perl's case:
  657.   #
  658.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2005-0448
  659.   #   http://www.cve.mitre.org/cgi-bin/cvename.cgi?name=CAN-2004-0452
  660.   #
  661.   # For fileutils.rb, this vulnerability is reported in [ruby-dev:26100].
  662.   #
  663.   def remove_entry_secure(path, force = false)
  664.     unless fu_have_symlink?
  665.       remove_entry path, force
  666.       return
  667.     end
  668.     fullpath = File.expand_path(path)
  669.     st = File.lstat(fullpath)
  670.     unless st.directory?
  671.       File.unlink fullpath
  672.       return
  673.     end
  674.     # is a directory.
  675.     parent_st = File.stat(File.dirname(fullpath))
  676.     unless fu_world_writable?(parent_st)
  677.       remove_entry path, force
  678.       return
  679.     end
  680.     unless parent_st.sticky?
  681.       raise ArgumentError, "parent directory is world writable, FileUtils#remove_entry_secure does not work; abort: #{path.inspect} (parent directory mode #{'%o' % parent_st.mode})"
  682.     end
  683.     # freeze tree root
  684.     euid = Process.euid
  685.     File.open(fullpath + '/.') {|f|
  686.       unless fu_stat_identical_entry?(st, f.stat)
  687.         # symlink (TOC-to-TOU attack?)
  688.         File.unlink fullpath
  689.         return
  690.       end
  691.       f.chown euid, -1
  692.       f.chmod 0700
  693.     }
  694.     # ---- tree root is frozen ----
  695.     root = Entry_.new(path)
  696.     root.preorder_traverse do |ent|
  697.       if ent.directory?
  698.         ent.chown euid, -1
  699.         ent.chmod 0700
  700.       end
  701.     end
  702.     root.postorder_traverse do |ent|
  703.       begin
  704.         ent.remove
  705.       rescue
  706.         raise unless force
  707.       end
  708.     end
  709.   rescue
  710.     raise unless force
  711.   end
  712.   module_function :remove_entry_secure
  713.  
  714.   def fu_world_writable?(st)
  715.     (st.mode & 0002) != 0
  716.   end
  717.   private_module_function :fu_world_writable?
  718.  
  719.   def fu_have_symlink?   #:nodoc
  720.     File.symlink nil, nil
  721.   rescue NotImplementedError
  722.     return false
  723.   rescue
  724.     return true
  725.   end
  726.   private_module_function :fu_have_symlink?
  727.  
  728.   def fu_stat_identical_entry?(a, b)   #:nodoc:
  729.     a.dev == b.dev and a.ino == b.ino
  730.   end
  731.   private_module_function :fu_stat_identical_entry?
  732.  
  733.   #
  734.   # This method removes a file system entry +path+.
  735.   # +path+ might be a regular file, a directory, or something.
  736.   # If +path+ is a directory, remove it recursively.
  737.   #
  738.   # See also #remove_entry_secure.
  739.   #
  740.   def remove_entry(path, force = false)
  741.     Entry_.new(path).postorder_traverse do |ent|
  742.       begin
  743.         ent.remove
  744.       rescue
  745.         raise unless force
  746.       end
  747.     end
  748.   rescue
  749.     raise unless force
  750.   end
  751.   module_function :remove_entry
  752.  
  753.   #
  754.   # Removes a file +path+.
  755.   # This method ignores StandardError if +force+ is true.
  756.   #
  757.   def remove_file(path, force = false)
  758.     Entry_.new(path).remove_file
  759.   rescue
  760.     raise unless force
  761.   end
  762.   module_function :remove_file
  763.  
  764.   #
  765.   # Removes a directory +dir+ and its contents recursively.
  766.   # This method ignores StandardError if +force+ is true.
  767.   #
  768.   def remove_dir(path, force = false)
  769.     remove_entry path, force   # FIXME?? check if it is a directory
  770.   end
  771.   module_function :remove_dir
  772.  
  773.   #
  774.   # Returns true if the contents of a file A and a file B are identical.
  775.   # 
  776.   #   FileUtils.compare_file('somefile', 'somefile')  #=> true
  777.   #   FileUtils.compare_file('/bin/cp', '/bin/mv')    #=> maybe false
  778.   #
  779.   def compare_file(a, b)
  780.     return false unless File.size(a) == File.size(b)
  781.     File.open(a, 'rb') {|fa|
  782.       File.open(b, 'rb') {|fb|
  783.         return compare_stream(fa, fb)
  784.       }
  785.     }
  786.   end
  787.   module_function :compare_file
  788.  
  789.   alias identical? compare_file
  790.   alias cmp compare_file
  791.   module_function :identical?
  792.   module_function :cmp
  793.  
  794.   #
  795.   # Returns true if the contents of a stream +a+ and +b+ are identical.
  796.   #
  797.   def compare_stream(a, b)
  798.     bsize = fu_stream_blksize(a, b)
  799.     sa = sb = nil
  800.     while sa == sb
  801.       sa = a.read(bsize)
  802.       sb = b.read(bsize)
  803.       unless sa and sb
  804.         if sa.nil? and sb.nil?
  805.           return true
  806.         end
  807.       end
  808.     end
  809.     false
  810.   end
  811.   module_function :compare_stream
  812.  
  813.   #
  814.   # Options: mode preserve noop verbose
  815.   # 
  816.   # If +src+ is not same as +dest+, copies it and changes the permission
  817.   # mode to +mode+.  If +dest+ is a directory, destination is +dest+/+src+.
  818.   # This method removes destination before copy.
  819.   # 
  820.   #   FileUtils.install 'ruby', '/usr/local/bin/ruby', :mode => 0755, :verbose => true
  821.   #   FileUtils.install 'lib.rb', '/usr/local/lib/ruby/site_ruby', :verbose => true
  822.   # 
  823.   def install(src, dest, options = {})
  824.     fu_check_options options, OPT_TABLE['install']
  825.     fu_output_message "install -c#{options[:preserve] && ' -p'}#{options[:mode] ? (' -m 0%o' % options[:mode]) : ''} #{[src,dest].flatten.join ' '}" if options[:verbose]
  826.     return if options[:noop]
  827.     fu_each_src_dest(src, dest) do |s, d|
  828.       unless File.exist?(d) and compare_file(s, d)
  829.         remove_file d, true
  830.         st = File.stat(s) if options[:preserve]
  831.         copy_file s, d
  832.         File.utime st.atime, st.mtime, d if options[:preserve]
  833.         File.chmod options[:mode], d if options[:mode]
  834.       end
  835.     end
  836.   end
  837.   module_function :install
  838.  
  839.   OPT_TABLE['install'] = [:mode, :preserve, :noop, :verbose]
  840.  
  841.   #
  842.   # Options: noop verbose
  843.   # 
  844.   # Changes permission bits on the named files (in +list+) to the bit pattern
  845.   # represented by +mode+.
  846.   # 
  847.   #   FileUtils.chmod 0755, 'somecommand'
  848.   #   FileUtils.chmod 0644, %w(my.rb your.rb his.rb her.rb)
  849.   #   FileUtils.chmod 0755, '/usr/bin/ruby', :verbose => true
  850.   # 
  851.   def chmod(mode, list, options = {})
  852.     fu_check_options options, OPT_TABLE['chmod']
  853.     list = fu_list(list)
  854.     fu_output_message sprintf('chmod %o %s', mode, list.join(' ')) if options[:verbose]
  855.     return if options[:noop]
  856.     list.each do |path|
  857.       Entry_.new(path).chmod mode
  858.     end
  859.   end
  860.   module_function :chmod
  861.  
  862.   OPT_TABLE['chmod'] = [:noop, :verbose]
  863.  
  864.   #
  865.   # Options: noop verbose force
  866.   # 
  867.   # Changes permission bits on the named files (in +list+)
  868.   # to the bit pattern represented by +mode+.
  869.   # 
  870.   #   FileUtils.chmod_R 0700, "/tmp/app.#{$$}"
  871.   # 
  872.   def chmod_R(mode, list, options = {})
  873.     fu_check_options options, OPT_TABLE['chmod_R']
  874.     list = fu_list(list)
  875.     fu_output_message sprintf('chmod -R%s %o %s',
  876.                               (options[:force] ? 'f' : ''),
  877.                               mode, list.join(' ')) if options[:verbose]
  878.     return if options[:noop]
  879.     list.each do |root|
  880.       Entry_.new(root).traverse do |ent|
  881.         begin
  882.           ent.chmod mode
  883.         rescue
  884.           raise unless options[:force]
  885.         end
  886.       end
  887.     end
  888.   end
  889.   module_function :chmod_R
  890.  
  891.   OPT_TABLE['chmod_R'] = [:noop, :verbose, :force]
  892.  
  893.   #
  894.   # Options: noop verbose
  895.   # 
  896.   # Changes owner and group on the named files (in +list+)
  897.   # to the user +user+ and the group +group+.  +user+ and +group+
  898.   # may be an ID (Integer/String) or a name (String).
  899.   # If +user+ or +group+ is nil, this method does not change
  900.   # the attribute.
  901.   # 
  902.   #   FileUtils.chown 'root', 'staff', '/usr/local/bin/ruby'
  903.   #   FileUtils.chown nil, 'bin', Dir.glob('/usr/bin/*'), :verbose => true
  904.   # 
  905.   def chown(user, group, list, options = {})
  906.     fu_check_options options, OPT_TABLE['chown']
  907.     list = fu_list(list)
  908.     fu_output_message sprintf('chown %s%s',
  909.                               [user,group].compact.join(':') + ' ',
  910.                               list.join(' ')) if options[:verbose]
  911.     return if options[:noop]
  912.     uid = fu_get_uid(user)
  913.     gid = fu_get_gid(group)
  914.     list.each do |path|
  915.       Entry_.new(path).chown uid, gid
  916.     end
  917.   end
  918.   module_function :chown
  919.  
  920.   OPT_TABLE['chown'] = [:noop, :verbose]
  921.  
  922.   #
  923.   # Options: noop verbose force
  924.   # 
  925.   # Changes owner and group on the named files (in +list+)
  926.   # to the user +user+ and the group +group+ recursively.
  927.   # +user+ and +group+ may be an ID (Integer/String) or
  928.   # a name (String).  If +user+ or +group+ is nil, this
  929.   # method does not change the attribute.
  930.   # 
  931.   #   FileUtils.chown_R 'www', 'www', '/var/www/htdocs'
  932.   #   FileUtils.chown_R 'cvs', 'cvs', '/var/cvs', :verbose => true
  933.   # 
  934.   def chown_R(user, group, list, options = {})
  935.     fu_check_options options, OPT_TABLE['chown_R']
  936.     list = fu_list(list)
  937.     fu_output_message sprintf('chown -R%s %s%s',
  938.                               (options[:force] ? 'f' : ''),
  939.                               [user,group].compact.join(':') + ' ',
  940.                               list.join(' ')) if options[:verbose]
  941.     return if options[:noop]
  942.     uid = fu_get_uid(user)
  943.     gid = fu_get_gid(group)
  944.     return unless uid or gid
  945.     list.each do |root|
  946.       Entry_.new(root).traverse do |ent|
  947.         begin
  948.           ent.chown uid, gid
  949.         rescue
  950.           raise unless options[:force]
  951.         end
  952.       end
  953.     end
  954.   end
  955.   module_function :chown_R
  956.  
  957.   OPT_TABLE['chown_R'] = [:noop, :verbose, :force]
  958.  
  959.   begin
  960.     require 'etc'
  961.  
  962.     def fu_get_uid(user)   #:nodoc:
  963.       return nil unless user
  964.       user = user.to_s
  965.       if /\A\d+\z/ =~ user
  966.       then user.to_i
  967.       else Etc.getpwnam(user).uid
  968.       end
  969.     end
  970.     private_module_function :fu_get_uid
  971.  
  972.     def fu_get_gid(group)   #:nodoc:
  973.       return nil unless group
  974.       if /\A\d+\z/ =~ group
  975.       then group.to_i
  976.       else Etc.getgrnam(group).gid
  977.       end
  978.     end
  979.     private_module_function :fu_get_gid
  980.  
  981.   rescue LoadError
  982.     # need Win32 support???
  983.  
  984.     def fu_get_uid(user)   #:nodoc:
  985.       user    # FIXME
  986.     end
  987.     private_module_function :fu_get_uid
  988.  
  989.     def fu_get_gid(group)   #:nodoc:
  990.       group   # FIXME
  991.     end
  992.     private_module_function :fu_get_gid
  993.   end
  994.  
  995.   #
  996.   # Options: noop verbose
  997.   # 
  998.   # Updates modification time (mtime) and access time (atime) of file(s) in
  999.   # +list+.  Files are created if they don't exist.
  1000.   # 
  1001.   #   FileUtils.touch 'timestamp'
  1002.   #   FileUtils.touch Dir.glob('*.c');  system 'make'
  1003.   # 
  1004.   def touch(list, options = {})
  1005.     fu_check_options options, OPT_TABLE['touch']
  1006.     list = fu_list(list)
  1007.     created = nocreate = options[:nocreate]
  1008.     t = options[:mtime]
  1009.     if options[:verbose]
  1010.       fu_output_message "touch #{nocreate ? ' -c' : ''}#{t ? t.strftime(' -t %Y%m%d%H%M.%S') : ''}#{list.join ' '}"
  1011.     end
  1012.     return if options[:noop]
  1013.     list.each do |path|
  1014.       created = nocreate
  1015.       begin
  1016.         File.utime(t, t, path)
  1017.       rescue Errno::ENOENT
  1018.         raise if created
  1019.         File.open(path, 'a') {
  1020.           ;
  1021.         }
  1022.         created = true
  1023.         retry if t
  1024.       end
  1025.     end
  1026.   end
  1027.   module_function :touch
  1028.  
  1029.   OPT_TABLE['touch'] = [:noop, :verbose, :mtime, :nocreate]
  1030.  
  1031.   private
  1032.  
  1033.   module StreamUtils_
  1034.     private
  1035.  
  1036.     def fu_windows?
  1037.       /mswin|mingw|bccwin|wince|emx/ =~ RUBY_PLATFORM
  1038.     end
  1039.  
  1040.     def fu_copy_stream0(src, dest, blksize)   #:nodoc:
  1041.       # FIXME: readpartial?
  1042.       while s = src.read(blksize)
  1043.         dest.write s
  1044.       end
  1045.     end
  1046.  
  1047.     def fu_stream_blksize(*streams)
  1048.       streams.each do |s|
  1049.         next unless s.respond_to?(:stat)
  1050.         size = fu_blksize(s.stat)
  1051.         return size if size
  1052.       end
  1053.       fu_default_blksize()
  1054.     end
  1055.  
  1056.     def fu_blksize(st)
  1057.       s = st.blksize
  1058.       return nil unless s
  1059.       return nil if s == 0
  1060.       s
  1061.     end
  1062.  
  1063.     def fu_default_blksize
  1064.       1024
  1065.     end
  1066.   end
  1067.  
  1068.   include StreamUtils_
  1069.   extend StreamUtils_
  1070.  
  1071.   class Entry_   #:nodoc: internal use only
  1072.     include StreamUtils_
  1073.  
  1074.     def initialize(a, b = nil, deref = false)
  1075.       @prefix = @rel = @path = nil
  1076.       if b
  1077.         @prefix = a
  1078.         @rel = b
  1079.       else
  1080.         @path = a
  1081.       end
  1082.       @deref = deref
  1083.       @stat = nil
  1084.       @lstat = nil
  1085.     end
  1086.  
  1087.     def inspect
  1088.       "\#<#{self.class} #{path()}>"
  1089.     end
  1090.  
  1091.     def path
  1092.       if @path
  1093.         @path.to_str
  1094.       else
  1095.         join(@prefix, @rel)
  1096.       end
  1097.     end
  1098.  
  1099.     def prefix
  1100.       @prefix || @path
  1101.     end
  1102.  
  1103.     def rel
  1104.       @rel
  1105.     end
  1106.  
  1107.     def dereference?
  1108.       @deref
  1109.     end
  1110.  
  1111.     def exist?
  1112.       lstat! ? true : false
  1113.     end
  1114.  
  1115.     def file?
  1116.       s = lstat!
  1117.       s and s.file?
  1118.     end
  1119.  
  1120.     def directory?
  1121.       s = lstat!
  1122.       s and s.directory?
  1123.     end
  1124.  
  1125.     def symlink?
  1126.       s = lstat!
  1127.       s and s.symlink?
  1128.     end
  1129.  
  1130.     def chardev?
  1131.       s = lstat!
  1132.       s and s.chardev?
  1133.     end
  1134.  
  1135.     def blockdev?
  1136.       s = lstat!
  1137.       s and s.blockdev?
  1138.     end
  1139.  
  1140.     def socket?
  1141.       s = lstat!
  1142.       s and s.socket?
  1143.     end
  1144.  
  1145.     def pipe?
  1146.       s = lstat!
  1147.       s and s.pipe?
  1148.     end
  1149.  
  1150.     S_IF_DOOR = 0xD000
  1151.  
  1152.     def door?
  1153.       s = lstat!
  1154.       s and (s.mode & 0xF000 == S_IF_DOOR)
  1155.     end
  1156.  
  1157.     def entries
  1158.       Dir.entries(path())\
  1159.           .reject {|n| n == '.' or n == '..' }\
  1160.           .map {|n| Entry_.new(prefix(), join(rel(), n.untaint)) }
  1161.     end
  1162.  
  1163.     def stat
  1164.       return @stat if @stat
  1165.       if lstat() and lstat().symlink?
  1166.         @stat = File.stat(path())
  1167.       else
  1168.         @stat = lstat()
  1169.       end
  1170.       @stat
  1171.     end
  1172.  
  1173.     def stat!
  1174.       return @stat if @stat
  1175.       if lstat! and lstat!.symlink?
  1176.         @stat = File.stat(path())
  1177.       else
  1178.         @stat = lstat!
  1179.       end
  1180.       @stat
  1181.     rescue SystemCallError
  1182.       nil
  1183.     end
  1184.  
  1185.     def lstat
  1186.       if dereference?
  1187.         @lstat ||= File.stat(path())
  1188.       else
  1189.         @lstat ||= File.lstat(path())
  1190.       end
  1191.     end
  1192.  
  1193.     def lstat!
  1194.       lstat()
  1195.     rescue SystemCallError
  1196.       nil
  1197.     end
  1198.  
  1199.     def chmod(mode)
  1200.       if symlink?
  1201.         File.lchmod mode, path() if have_lchmod?
  1202.       else
  1203.         File.chmod mode, path()
  1204.       end
  1205.     end
  1206.  
  1207.     def chown(uid, gid)
  1208.       if symlink?
  1209.         File.lchown uid, gid, path() if have_lchown?
  1210.       else
  1211.         File.chown uid, gid, path()
  1212.       end
  1213.     end
  1214.  
  1215.     def copy(dest)
  1216.       case
  1217.       when file?
  1218.         copy_file dest
  1219.       when directory?
  1220.         begin
  1221.           Dir.mkdir dest
  1222.         rescue
  1223.           raise unless File.directory?(dest)
  1224.         end
  1225.       when symlink?
  1226.         File.symlink File.readlink(path()), dest
  1227.       when chardev?
  1228.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1229.         mknod dest, ?c, 0666, lstat().rdev
  1230.       when blockdev?
  1231.         raise "cannot handle device file" unless File.respond_to?(:mknod)
  1232.         mknod dest, ?b, 0666, lstat().rdev
  1233.       when socket?
  1234.         raise "cannot handle socket" unless File.respond_to?(:mknod)
  1235.         mknod dest, nil, lstat().mode, 0
  1236.       when pipe?
  1237.         raise "cannot handle FIFO" unless File.respond_to?(:mkfifo)
  1238.         mkfifo dest, 0666
  1239.       when door?
  1240.         raise "cannot handle door: #{path()}"
  1241.       else
  1242.         raise "unknown file type: #{path()}"
  1243.       end
  1244.     end
  1245.  
  1246.     def copy_file(dest)
  1247.       st = stat()
  1248.       File.open(path(),  'rb') {|r|
  1249.         File.open(dest, 'wb', st.mode) {|w|
  1250.           fu_copy_stream0 r, w, (fu_blksize(st) || fu_default_blksize())
  1251.         }
  1252.       }
  1253.     end
  1254.  
  1255.     def copy_metadata(path)
  1256.       st = lstat()
  1257.       File.utime st.atime, st.mtime, path
  1258.       begin
  1259.         File.chown st.uid, st.gid, path
  1260.       rescue Errno::EPERM
  1261.         # clear setuid/setgid
  1262.         File.chmod st.mode & 01777, path
  1263.       else
  1264.         File.chmod st.mode, path
  1265.       end
  1266.     end
  1267.  
  1268.     def remove
  1269.       if directory?
  1270.         remove_dir1
  1271.       else
  1272.         remove_file
  1273.       end
  1274.     end
  1275.  
  1276.     def remove_dir1
  1277.       platform_support {
  1278.         Dir.rmdir path().sub(%r</\z>, '')
  1279.       }
  1280.     end
  1281.  
  1282.     def remove_file
  1283.       platform_support {
  1284.         File.unlink path
  1285.       }
  1286.     end
  1287.  
  1288.     def platform_support
  1289.       return yield unless fu_windows?
  1290.       first_time_p = true
  1291.       begin
  1292.         yield
  1293.       rescue Errno::ENOENT
  1294.         raise
  1295.       rescue => err
  1296.         if first_time_p
  1297.           first_time_p = false
  1298.           begin
  1299.             File.chmod 0700, path()   # Windows does not have symlink
  1300.             retry
  1301.           rescue SystemCallError
  1302.           end
  1303.         end
  1304.         raise err
  1305.       end
  1306.     end
  1307.  
  1308.     def preorder_traverse
  1309.       stack = [self]
  1310.       while ent = stack.pop
  1311.         yield ent
  1312.         stack.concat ent.entries.reverse if ent.directory?
  1313.       end
  1314.     end
  1315.  
  1316.     alias traverse preorder_traverse
  1317.  
  1318.     def postorder_traverse
  1319.       if directory?
  1320.         entries().each do |ent|
  1321.           ent.postorder_traverse do |e|
  1322.             yield e
  1323.           end
  1324.         end
  1325.       end
  1326.       yield self
  1327.     end
  1328.  
  1329.     private
  1330.  
  1331.     $fileutils_rb_have_lchmod = nil
  1332.  
  1333.     def have_lchmod?
  1334.       # This is not MT-safe, but it does not matter.
  1335.       if $fileutils_rb_have_lchmod == nil
  1336.         $fileutils_rb_have_lchmod = check_have_lchmod?
  1337.       end
  1338.       $fileutils_rb_have_lchmod
  1339.     end
  1340.  
  1341.     def check_have_lchmod?
  1342.       return false unless File.respond_to?(:lchmod)
  1343.       File.lchmod 0
  1344.       return true
  1345.     rescue NotImplementedError
  1346.       return false
  1347.     end
  1348.  
  1349.     $fileutils_rb_have_lchown = nil
  1350.  
  1351.     def have_lchown?
  1352.       # This is not MT-safe, but it does not matter.
  1353.       if $fileutils_rb_have_lchown == nil
  1354.         $fileutils_rb_have_lchown = check_have_lchown?
  1355.       end
  1356.       $fileutils_rb_have_lchown
  1357.     end
  1358.  
  1359.     def check_have_lchown?
  1360.       return false unless File.respond_to?(:lchown)
  1361.       File.lchown nil, nil
  1362.       return true
  1363.     rescue NotImplementedError
  1364.       return false
  1365.     end
  1366.  
  1367.     def join(dir, base)
  1368.       return dir.to_str if not base or base == '.'
  1369.       return base.to_str if not dir or dir == '.'
  1370.       File.join(dir, base)
  1371.     end
  1372.   end   # class Entry_
  1373.  
  1374.   def fu_list(arg)   #:nodoc:
  1375.     [arg].flatten.map {|path| path.to_str }
  1376.   end
  1377.   private_module_function :fu_list
  1378.  
  1379.   def fu_each_src_dest(src, dest)   #:nodoc:
  1380.     fu_each_src_dest0(src, dest) do |s, d|
  1381.       raise ArgumentError, "same file: #{s} and #{d}" if fu_same?(s, d)
  1382.       yield s, d
  1383.     end
  1384.   end
  1385.   private_module_function :fu_each_src_dest
  1386.  
  1387.   def fu_each_src_dest0(src, dest)   #:nodoc:
  1388.     if src.is_a?(Array)
  1389.       src.each do |s|
  1390.         s = s.to_str
  1391.         yield s, File.join(dest, File.basename(s))
  1392.       end
  1393.     else
  1394.       src = src.to_str
  1395.       if File.directory?(dest)
  1396.         yield src, File.join(dest, File.basename(src))
  1397.       else
  1398.         yield src, dest.to_str
  1399.       end
  1400.     end
  1401.   end
  1402.   private_module_function :fu_each_src_dest0
  1403.  
  1404.   def fu_same?(a, b)   #:nodoc:
  1405.     if fu_have_st_ino?
  1406.       st1 = File.stat(a)
  1407.       st2 = File.stat(b)
  1408.       st1.dev == st2.dev and st1.ino == st2.ino
  1409.     else
  1410.       File.expand_path(a) == File.expand_path(b)
  1411.     end
  1412.   rescue Errno::ENOENT
  1413.     return false
  1414.   end
  1415.   private_module_function :fu_same?
  1416.  
  1417.   def fu_have_st_ino?   #:nodoc:
  1418.     not fu_windows?
  1419.   end
  1420.   private_module_function :fu_have_st_ino?
  1421.  
  1422.   def fu_check_options(options, optdecl)   #:nodoc:
  1423.     h = options.dup
  1424.     optdecl.each do |opt|
  1425.       h.delete opt
  1426.     end
  1427.     raise ArgumentError, "no such option: #{h.keys.join(' ')}" unless h.empty?
  1428.   end
  1429.   private_module_function :fu_check_options
  1430.  
  1431.   def fu_update_option(args, new)   #:nodoc:
  1432.     if args.last.is_a?(Hash)
  1433.       args[-1] = args.last.dup.update(new)
  1434.     else
  1435.       args.push new
  1436.     end
  1437.     args
  1438.   end
  1439.   private_module_function :fu_update_option
  1440.  
  1441.   @fileutils_output = $stderr
  1442.   @fileutils_label  = ''
  1443.  
  1444.   def fu_output_message(msg)   #:nodoc:
  1445.     @fileutils_output ||= $stderr
  1446.     @fileutils_label  ||= ''
  1447.     @fileutils_output.puts @fileutils_label + msg
  1448.   end
  1449.   private_module_function :fu_output_message
  1450.  
  1451.   #
  1452.   # Returns an Array of method names which have any options.
  1453.   #
  1454.   #   p FileUtils.commands  #=> ["chmod", "cp", "cp_r", "install", ...]
  1455.   #
  1456.   def FileUtils.commands
  1457.     OPT_TABLE.keys
  1458.   end
  1459.  
  1460.   #
  1461.   # Returns an Array of option names.
  1462.   #
  1463.   #   p FileUtils.options  #=> ["noop", "force", "verbose", "preserve", "mode"]
  1464.   #
  1465.   def FileUtils.options
  1466.     OPT_TABLE.values.flatten.uniq.map {|sym| sym.to_s }
  1467.   end
  1468.  
  1469.   #
  1470.   # Returns true if the method +mid+ have an option +opt+.
  1471.   #
  1472.   #   p FileUtils.have_option?(:cp, :noop)     #=> true
  1473.   #   p FileUtils.have_option?(:rm, :force)    #=> true
  1474.   #   p FileUtils.have_option?(:rm, :perserve) #=> false
  1475.   #
  1476.   def FileUtils.have_option?(mid, opt)
  1477.     li = OPT_TABLE[mid.to_s] or raise ArgumentError, "no such method: #{mid}"
  1478.     li.include?(opt)
  1479.   end
  1480.  
  1481.   #
  1482.   # Returns an Array of option names of the method +mid+.
  1483.   #
  1484.   #   p FileUtils.options(:rm)  #=> ["noop", "verbose", "force"]
  1485.   #
  1486.   def FileUtils.options_of(mid)
  1487.     OPT_TABLE[mid.to_s].map {|sym| sym.to_s }
  1488.   end
  1489.  
  1490.   #
  1491.   # Returns an Array of method names which have the option +opt+.
  1492.   #
  1493.   #   p FileUtils.collect_method(:preserve) #=> ["cp", "cp_r", "copy", "install"]
  1494.   #
  1495.   def FileUtils.collect_method(opt)
  1496.     OPT_TABLE.keys.select {|m| OPT_TABLE[m].include?(opt) }
  1497.   end
  1498.  
  1499.   METHODS = singleton_methods() - %w( private_module_function
  1500.       commands options have_option? options_of collect_method )
  1501.  
  1502.   # 
  1503.   # This module has all methods of FileUtils module, but it outputs messages
  1504.   # before acting.  This equates to passing the <tt>:verbose</tt> flag to
  1505.   # methods in FileUtils.
  1506.   # 
  1507.   module Verbose
  1508.     include FileUtils
  1509.     @fileutils_output  = $stderr
  1510.     @fileutils_label   = ''
  1511.     ::FileUtils.collect_method(:verbose).each do |name|
  1512.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1513.         def #{name}(*args)
  1514.           super(*fu_update_option(args, :verbose => true))
  1515.         end
  1516.         private :#{name}
  1517.       EOS
  1518.     end
  1519.     extend self
  1520.     class << self
  1521.       ::FileUtils::METHODS.each do |m|
  1522.         public m
  1523.       end
  1524.     end
  1525.   end
  1526.  
  1527.   # 
  1528.   # This module has all methods of FileUtils module, but never changes
  1529.   # files/directories.  This equates to passing the <tt>:noop</tt> flag
  1530.   # to methods in FileUtils.
  1531.   # 
  1532.   module NoWrite
  1533.     include FileUtils
  1534.     @fileutils_output  = $stderr
  1535.     @fileutils_label   = ''
  1536.     ::FileUtils.collect_method(:noop).each do |name|
  1537.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1538.         def #{name}(*args)
  1539.           super(*fu_update_option(args, :noop => true))
  1540.         end
  1541.         private :#{name}
  1542.       EOS
  1543.     end
  1544.     extend self
  1545.     class << self
  1546.       ::FileUtils::METHODS.each do |m|
  1547.         public m
  1548.       end
  1549.     end
  1550.   end
  1551.  
  1552.   # 
  1553.   # This module has all methods of FileUtils module, but never changes
  1554.   # files/directories, with printing message before acting.
  1555.   # This equates to passing the <tt>:noop</tt> and <tt>:verbose</tt> flag
  1556.   # to methods in FileUtils.
  1557.   # 
  1558.   module DryRun
  1559.     include FileUtils
  1560.     @fileutils_output  = $stderr
  1561.     @fileutils_label   = ''
  1562.     ::FileUtils.collect_method(:noop).each do |name|
  1563.       module_eval(<<-EOS, __FILE__, __LINE__ + 1)
  1564.         def #{name}(*args)
  1565.           super(*fu_update_option(args, :noop => true, :verbose => true))
  1566.         end
  1567.         private :#{name}
  1568.       EOS
  1569.     end
  1570.     extend self
  1571.     class << self
  1572.       ::FileUtils::METHODS.each do |m|
  1573.         public m
  1574.       end
  1575.     end
  1576.   end
  1577.  
  1578. end
  1579.